# -*- coding: utf-8 -*-
"""
该排行榜适用于区服和跨服服务

原理：拼接 score, Timestamp

     插入过程：传入排行榜名，唯一ID（ID长度要一致),当前时间戳，分数，将Score和Timestamp用乘法拼接，在插入排行榜

     读取某一条记录过程：传入排行榜名，唯一ID，将获取的score分解得到实际score

     读取整个排行榜：直接获取实际排行榜，将获取的score分解得到实际score

     移除某一条记录过程：只移除实际排行榜中的记录

ZADD key score member
注: 1.角色榜单的member必须拼接server_id,即为: rid|server_id（只需在value2传入server_id就会自行转换）
    2.同盟国国家榜member格式为allies_id|country_id
Module Description: 排行榜相关
Date: 2020/09/27
Author: sxj
"""

import time
import framework.distribute.mutex as mutex

import framework.distribute.area_lock_define as behavior_lock_define


# 反序时间戳基数，11位
# 原理：当前正序时间戳为10位，用11位减去10位可将后来的时间戳反转为更小的值，从而使其排序在先来的时间戳之后
from framework.dal.repository_impl import RoleRepository
from framework.firefly_dc.singleton import Singleton


TIME_BASE = 10000000000

# 时间戳最小值，10位
# 限制时间戳最小值防止时间戳位数变化导致排位混乱
# 从当前时间到最小值还有约8000000000秒，折合253年，余量足够
TIME_BASE_LIMIT = 1000000000


class ShareRankManger(object):
    """
    score 由2个整数条件（score1, score2）拼接的榜单管理基类类
    join_scale:     1.score = score1 * join_scale + score2
                    2.score为实际存入redis的数据
                    3.join_scale必须大于score2的最大取值
                    4.score2 必须 >= 0
    redis存储格式：
                value : object id (比如role_id, alliance_id)
                score : (score1 * join_scale + score2) if score1 >=0 else (score1 * self.join_scale + (score2 - self.join_scale))
    """

    __metaclass__ = Singleton

    def __init__(self, join_scale=100000000000):
        from framework.redisutils.redis_storage import RedisStorage
        self.redis = RedisStorage.get_conn('lock')
        self.join_scale = join_scale

    def get_join_score(self, score1, score2):
        if score1 >= 0:
            return score1 * self.join_scale + score2
        else:
            return score1 * self.join_scale + (score2 - self.join_scale)

    @staticmethod
    def format_two_value(value, value2):
        if value2:
            return value + '|' + str(value2)
        return value

    @staticmethod
    def get_times_tamp(time_reverse=True, order_tamp=None):
        order_tamp = order_tamp if order_tamp else int(time.time())
        if time_reverse:
            timestamp = TIME_BASE - order_tamp
            # 限制位数
            if timestamp < TIME_BASE_LIMIT:
                timestamp = TIME_BASE_LIMIT
            return timestamp
        else:
            return order_tamp

    def add_to_rank(self, rank_type, value, score1, score2, value2=None):
        """
        添加单条记录到排行榜
        :param value: rid or alliance_id ...
        :param score1: 第一排序值
        :param score2: 第二排序值
        :param rank_type: 榜单key
        :param value2: 需要和value拼接的
        :return:
        """

        self.redis.zadd(rank_type, self.get_join_score(score1, score2), self.format_two_value(value, value2))

    def add_score_to_rank(self, value, score, rank_type, need_lock=True, value2=None, time_reverse=True, score2=None):
        """添加到排行榜
        1.可选是否检测角色状态
        2.插入时加锁，防止玩家操作和后台冻结角色并发
        :param value: string RoleGuid
        :param score: int 战斗力
        :param rank_type: str 排行榜类型 'FightCap': 战斗力,'Kill': 杀敌数, 'Alliance': 联盟战斗力 ...
        :param need_lock: 是否加锁
        :param value2: 例: server_id 用于拼接 如: value|value2 --> rid|server_id
        :param time_reverse: 时间戳是否反序（默认先到排在前面）
        :param score2: 第二排序值（默认score2小的排前面，配合time_reverse使用）
        """
        score = score or 0
        timestamp = self.get_times_tamp(time_reverse=time_reverse, order_tamp=score2)
        # 排行榜中插入新记录
        if need_lock:
            with mutex.KeyMutex(behavior_lock_define.RankLock.U_RANK_MODIFY_LOCK.format(RoleGuid=value)):
                self.add_to_rank(rank_type, value, score, timestamp, value2=value2)
        else:
            self.add_to_rank(rank_type, value, score, timestamp, value2=value2)

    def add_list_to_rank(self, rank_type, data_list):
        """
        插入多条记录到排行榜
        :param rank_type:
        :param data_list: [[score1, score2, value], [score1, score2, value]]
        :return:
        """
        data = []
        for score1, score2, value in data_list:
            data.append(self.get_join_score(score1, score2))
            data.append(value)

        self.redis.zadd(rank_type, *data)

    def add_list_to_rank_by_two_value(self, rank_type, data_list):
        """
        插入多条记录到排行榜
        :param rank_type:
        :param data_list: [[score1, score2, value, value2], [score1, score2, value, value2]]
        :return:
        """
        data = []
        for score1, score2, value, value2 in data_list:
            data.append(self.get_join_score(score1, score2))
            data.append(self.format_two_value(value, value2))

        self.redis.zadd(rank_type, *data)

    def get_range(self, rank_type, start=0, end=99):
        """获取榜单记录"""
        data_list = self.redis.zrevrange(rank_type, start, end, True)
        return map(lambda data: [data[0], int(data[1] / self.join_scale)], data_list)

    def get_range_by_two_value(self, rank_type, start=0, end=99):
        """获取榜单记录"""
        data_list = self.redis.zrevrange(rank_type, start, end, True)
        new_list = []
        for data in data_list:
            value, value2 = data[0].split('|')
            new_list.append([value, int(data[1] / self.join_scale), value2])
        return new_list

    def get_rank(self, rank_type, value, value2=None):
        """获取个体排名"""
        rank = self.redis.zrevrank(rank_type, self.format_two_value(value, value2))
        return rank + 1 if rank is not None else 0

    def get_score(self, rank_type, value, value2=None):
        """获取某个个体某个排行榜的分数"""
        score = self.redis.zscore(rank_type, self.format_two_value(value, value2))
        return int(score / self.join_scale) if score else 0

    def add_sign_to_rank_incr(self, rank_type, value, score1, value2=None):
        """
        增加排行榜某个个体的分数
        个体: 玩家，联盟，同盟国国家
        :param rank_type:
        :param value:
        :param score1: 增量
        :param value2: 例: server_id 用于拼接 如: value|value2 --> rid|server_id
        :return:
        """
        score_base = self.get_score(rank_type, value, value2=value2)
        score = score1 + score_base
        self.add_score_to_rank(value, score, rank_type, value2=value2)

    def set_expire_time(self, rank_type, expire_time):
        """
        设置TTL
        :param rank_type: set_key
        :param expire_time:
        :return:
        """
        self.redis.expire(rank_type, expire_time)

    def get_number(self, rank_type):
        """获取排行榜数量"""
        return self.redis.zcard(rank_type)

    def del_from_set(self, rank_type, key, value2=None):
        """
        从set中直接删除某记录
        :param key: set_key
        :param rank_type: set_name
        :param value2:
        :return:
        """
        self.redis.zrem(rank_type, self.format_two_value(key, value2))

    def del_from_rank(self, value, rank_type, value2=None):
        """
        从排行榜中删除某记录
        :param value:玩家排行榜(RoleGuid)/联盟排行榜(AllianceId)/同盟国国家排行榜(AlliesId)
        :param rank_type:
        :param value2: 区服id/国家id
        """
        with mutex.KeyMutex(behavior_lock_define.RankLock.U_RANK_MODIFY_LOCK.format(RoleGuid=value)):
            self.del_from_set(rank_type, value, value2=value2)

    def delete_whole_rank(self, rank_type):
        """删除整个排行榜"""
        self.redis.delete(rank_type)

    def remove_zero(self, rank_type):
        """删除排行榜中为0的记录"""
        self.redis.zremrangebyscore(rank_type, 0, 0)

    def del_by_out_of_count(self, rank_type, need_count):
        """
        删除超过need_count之后的记录， score从大到小
        :param rank_type: redis key
        :param need_count: 所需的前n名记录， 其余删除
        :return:
        """
        count = self.get_number(rank_type)
        del_max = count - need_count
        if del_max >= 1:
            self.redis.zremrangebyrank(rank_type, 0, del_max - 1)

    def get_set_range_by_score(self, rank_type, max_score='+inf', min_score=0, start=0, num=100):
        """
        返回rang where score between min and max
        顺序：从大到小
        :param rank_type: 榜单key
        :param max_score: 最大值
        :param min_score: 最小值
        :param start: 切片位置
        :param num: 元素个数
        :return:
        """
        time_stamp = self.get_times_tamp()
        return self.redis.zrevrangebyscore(rank_type, self.get_join_score(max_score, time_stamp),
                                           self.get_join_score(min_score, time_stamp), start=start, num=num,
                                           withscores=True)